# recommendation_pool/models.py
from django.db import models
from django.contrib.auth.models import User

from algorithm.models import UserProblemRecommendation, UserTagMastery
from management.updater import DataUpdater
from problems.models import Problem
from django.utils import timezone
from datetime import timedelta
from problems.models import Problem
from recommendation.models import SolvedProblem, RecommendationQueue
from django.db.transaction import on_commit
import threading
from django.db import IntegrityError


from recommendation_history.models import RecommendationHistory


class UserRecommendationPool(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    problem = models.ForeignKey(Problem, on_delete=models.CASCADE)

    class Meta:
        unique_together = ('user', 'problem')

    def __str__(self):
        return f"{self.user.username} - {self.problem.problem_number}"

    @classmethod
    def create_recommendation_entry(cls, user_id, problem_number):
        try:
            user = User.objects.get(id=user_id)
            problem = Problem.objects.get(problem_number=problem_number)
        except User.DoesNotExist:
            return None, f"No user found with ID {user_id}"
        except Problem.DoesNotExist:
            return None, f"No problem found with number {problem_number}"

        # Check if an entry already exists
        entry, created = cls.objects.get_or_create(user=user, problem=problem)

        if created:
            return entry, f"Created new recommendation entry for user {user.username} and problem {problem.problem_number}"
        else:
            return entry, f"Entry already exists for user {user.username} and problem {problem.problem_number}"

    @classmethod
    def init_user_recommendation_pool(cls, user_id):
        try:
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return f"没有找到ID为 {user_id} 的用户"

        # 获取用户已解决的题目集合
        solved_problem_numbers = SolvedProblem.objects.filter(user=user).values_list('problem__problem_number',
                                                                                     flat=True)

        # 查询未解决的题目
        unsolved_problems = Problem.objects.exclude(problem_number__in=solved_problem_numbers)

        # 更新或创建推荐池条目
        created_count = 0
        for problem in unsolved_problems:
            obj, created = cls.objects.update_or_create(user=user, problem=problem)
            if created:
                created_count += 1

        return f"用户 {user.username} 的推荐池已更新，新增 {created_count} 题目到推荐池。"

    @classmethod
    def pop_top_two_scores(cls, user_id):
        try:
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return None, None  # 如果用户不存在，返回两个 None

        # 获取该用户在推荐池中的题目
        pool_problems = cls.objects.filter(user=user).select_related('problem')

        top_score = float('-inf')
        second_top_score = float('-inf')
        top_problem = None
        second_top_problem = None

        # 遍历用户推荐池中的题目，查询对应的推荐分数
        for pool_problem in pool_problems:
            recommendation = UserProblemRecommendation.objects.filter(user=user, problem=pool_problem.problem).first()
            if recommendation and recommendation.recommendation_score > top_score:
                second_top_score = top_score
                second_top_problem = top_problem
                top_score = recommendation.recommendation_score
                top_problem = recommendation.problem
            elif recommendation and recommendation.recommendation_score > second_top_score:
                second_top_score = recommendation.recommendation_score
                second_top_problem = recommendation.problem

        UserRecommendationPool.delete_problem_from_pool(user_id, top_problem.problem_number)
        UserRecommendationPool.delete_problem_from_pool(user_id, second_top_problem.problem_number)
        return top_problem, second_top_problem

    @classmethod
    def pop_top_ten_scores(cls, user_id):
        try:
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return []  # 如果用户不存在，返回空列表

        # 获取用户推荐池中的所有题目
        pool_problems = cls.objects.filter(user=user).select_related('problem')

        # 获取用户的标签推荐分前5的标签
        top_tags = UserTagMastery.objects.filter(user=user).order_by('-recommendation_score')[:5]

        # 存储符合条件的推荐题目
        top_problems = []
        selected_problems_set = set()  # 使用集合来保证唯一性

        # 从每个标签推荐分前5的标签中，获取该标签下推荐池中前2个推荐分最高的题目
        for tag_mastery in top_tags:
            top_tag_problems = (
                UserProblemRecommendation.objects
                .filter(user=user, problem__in=[p.problem for p in pool_problems], problem__tags=tag_mastery.tag)
                .exclude(problem__in=selected_problems_set)  # 排除已选中的题目
                .order_by('-recommendation_score')[:2]
            )
            for rec in top_tag_problems:
                top_problems.append(rec.problem)
                selected_problems_set.add(rec.problem)

        # 如果不足10道，则从推荐池中的其他题目按推荐分补足
        if len(top_problems) < 10:
            needed = 10 - len(top_problems)
            additional_problems = (
                UserProblemRecommendation.objects
                .filter(user=user, problem__in=[p.problem for p in pool_problems])
                .exclude(problem__in=selected_problems_set)  # 排除已选中的题目
                .order_by('-recommendation_score')[:needed]
            )
            for rec in additional_problems:
                top_problems.append(rec.problem)
                selected_problems_set.add(rec.problem)

        # 删除推荐池中的已选题目
        for problem in top_problems:
            cls.delete_problem_from_pool(user_id, problem.problem_number)

        return top_problems
    # 为新队列类准备的get_ten函数，只从pool中找到10个值最大的，考虑的函数行为将其修改为get_top_ten
    # 再次考虑到责任划分，pool的ten方法需要保证不产出重复的题目，所以再次修改为 pop
    @classmethod
    def pop_top_ten_scores_old(cls, user_id):
        try:
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return []  # 如果用户不存在，返回空列表

        # 获取该用户在推荐池中的所有题目，包括它们的推荐分数
        pool_problems = cls.objects.filter(user=user).select_related('problem')
        # select_related 数据库优化方法，用于预先加载与每个 UserRecommendationPool 实例相关联的 Problem 对象
        recommendations = UserProblemRecommendation.objects.filter(
        user=user, problem__in=[p.problem for p in pool_problems]).order_by(
            '-recommendation_score'
        )[:10]

        top_problems = []
        for recommendation in recommendations:
            top_problems.append(recommendation.problem)
            # 从推荐池中删除每个题目
            UserRecommendationPool.delete_problem_from_pool(user_id, recommendation.problem.problem_number)

        return top_problems

    @classmethod
    def delete_problem_from_pool(cls, user_id, problem_number):
        # 删除用户推荐池中的指定题目
        try:
            user = User.objects.get(id=user_id)
            problem = Problem.objects.get(problem_number=problem_number)
            instance = cls.objects.get(user=user, problem=problem)
            instance.delete()
            return True  # 操作成功
        except User.DoesNotExist:
            print(f"Error: No user found with ID {user_id}")
        except Problem.DoesNotExist:
            print(f"Error: No problem found with number {problem_number}")
        except cls.DoesNotExist:
            print(
                f"Error: Problem number {problem_number} not found in the recommendation pool for user {user.username}")
        return False  # 发生异常，操作失败

    @classmethod
    def delete_all_user_entries(cls, user_id):
        # 尝试获取用户实例，确认用户存在
        try:
            user = User.objects.get(id=user_id)
            # 删除与该用户相关的所有记录
            cls.objects.filter(user=user).delete()
            return True, "All entries for user have been deleted."
        except User.DoesNotExist:
            # 如果用户不存在，返回错误信息
            return False, f"No user found with ID {user_id}"

class UserUndoPool(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    problem = models.ForeignKey(Problem, on_delete=models.CASCADE)
    timestamp = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ('user', 'problem')

    def __str__(self):
        return f"{self.user.username} - {self.problem.problem_number} - Undo at {self.timestamp}"

    @classmethod
    def check_and_restore(cls):
        timeout = timezone.now() - timedelta(days=1)  # 例如，一天后恢复
        expired_records = cls.objects.filter(timestamp__lte=timeout) # 小于或等于__lte: less than or equal to
        for record in expired_records:
            # UserRecommendationPool.objects.create(user=record.user, problem=record.problem)
            UserRecommendationPool.create_recommendation_entry(record.user.id,record.problem.problem_number)
            record.delete()
    # 所有的题目都进UndoPool，一个确定性数据检查方法，每次检查都删去那些不需要返回Pool的数据：真AC了的；
    # 需要一个新的检查和恢复方法，这个先对user_id对应的UndoPool数据检查，再执行检查每条数据时间戳的方法返回pool

    @classmethod
    def move_expired_to_recommendation_pool(cls, user_id):
        # 将指定用户超过一天的撤销记录移动到推荐池。
        try:
            timeout = timezone.now() - timedelta(days=1)
            user = User.objects.get(id=user_id)
            expired_records = cls.objects.filter(user=user, timestamp__lte=timeout)
            for record in expired_records:
                UserRecommendationPool.create_recommendation_entry(record.user.id, record.problem.problem_number)
                record.delete()
            return True, "Expired records moved to recommendation pool successfully."
        except User.DoesNotExist:
            return False, "User not found."
        except Exception as e:
            return False, f"Error occurred while moving expired records: {str(e)}"

    @classmethod
    def remove_if_solved(cls, user_id):
        # 如果用户已解决问题，则移除相应的撤销记录。
        try:
            user = User.objects.get(id=user_id)
            user_undo_records = cls.objects.filter(user=user)
            for record in user_undo_records:
                if SolvedProblem.objects.filter(user=user, problem=record.problem).exists():
                    record.delete()
            return True, "Solved problem records deleted successfully."
        except User.DoesNotExist:
            return False, "User not found."
        except SolvedProblem.DoesNotExist:
            return False, "Solved problem record does not exist."
        except Exception as e:
            return False, f"Error occurred while removing solved problem records: {str(e)}"

    @classmethod
    def delete_all_for_user(cls, user_id):
        # 删除所有该用户的 UserUndoPool 记录
        try:
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return False, f"No user found with ID {user_id}"

        try:
            # 删除所有该用户的 UserUndoPool 记录
            cls.objects.filter(user=user).delete()
            return True, "All UserUndoPool records for the user deleted successfully"
        except Exception as e:
            return False, f"Error deleting records: {str(e)}"




from django.db import models
from django.contrib.auth.models import User
from django.db.models import JSONField
from django.db import transaction


# 继承和扩展原有的队列类
class RecommendationPoolQueue(RecommendationQueue):
    # 已有的字段为 user,problem_numbers
    # 已有的函数为enqueue,dequeue,randomize_queue
    ac_list = JSONField(default=list)  # 存储已通过题目的列表
    buffer_problem_numbers = JSONField(default=list)
    state = models.IntegerField(default=0)  # 缓冲区的状态值，0表示缓冲区未就绪，1表示缓冲区就绪，2表示缓冲区正等待计算中
    current_problem = models.ForeignKey(Problem, on_delete=models.SET_NULL, null=True, default=None)

    # 该方法将会作为任务函数异步执行，功能是更新缓冲区
    from django.db import transaction
    import logging

    logger = logging.getLogger("django")

    @staticmethod
    def update_task(user_id):
        try:
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return False, f"No user found with ID {user_id}"

        try:
            # ======== 第一段事务：锁住 queue_instance，备份 & 清空 ac_list，置为计算中 ========
            with transaction.atomic():
                queue_instance = RecommendationPoolQueue.objects.select_for_update().get(user=user)

                if queue_instance.state == 2:
                    return False, "Buffer is currently being computed."
                elif queue_instance.state == 1:
                    return False, "Buffer is ready and does not need refresh."

                ac_list_backup = queue_instance.ac_list.copy()
                queue_instance.ac_list = []
                queue_instance.state = 2
                queue_instance.save()

            # ======== 第二段（非事务）：根据 ac_list 计算用户画像 ========
            try:
                problems = Problem.objects.filter(problem_number__in=ac_list_backup)
                DataUpdater.update_data_normal(user.id, list(problems))
                # logger.info(f"[User {user_id}] Data update completed.")
            except Exception as e:
                # logger.error(f"[User {user_id}] Data update failed: {str(e)}")
                print("[User {user_id}] Data update failed: {str(e)}")

            # ======== 第三段事务：更新推荐 buffer 和最终状态 ========
            try:
                top_problems = UserRecommendationPool.pop_top_ten_scores(user_id)

                with transaction.atomic():
                    queue_instance = RecommendationPoolQueue.objects.select_for_update().get(user=user)

                    if top_problems:
                        queue_instance.buffer_problem_numbers = [p.problem_number for p in top_problems]
                        queue_instance.state = 1  # 已就绪
                        queue_instance.save()
                        return True, "Buffer updated successfully."
                    else:
                        queue_instance.state = 0  # 重置状态为未就绪
                        queue_instance.save()
                        return False, "Not enough problems to update the buffer."

            except Exception as e:
                # logger.error(f"[User {user_id}] Buffer update failed: {str(e)}")
                with transaction.atomic():
                    queue_instance = RecommendationPoolQueue.objects.select_for_update().get(user=user)
                    queue_instance.state = 0
                    queue_instance.save()
                return False, f"Error updating buffer: {str(e)}"

        except RecommendationPoolQueue.DoesNotExist:
            return False, f"No recommendation queue found for user ID {user_id}"

    # 将缓冲区的数据赋值给队列
    def sync_from_buffer_to_queue(self):
        if self.state == 1:
            # 当缓冲区就绪且主队列为空时，才进行同步
            if len(self.problem_numbers) == 0:  # 使用len()检查队列是否为空
                # 将buffer_problem_numbers赋值给problem_numbers
                self.problem_numbers = self.buffer_problem_numbers.copy()  # 拷贝buffer内容到主队列
                self.buffer_problem_numbers = []  # 清空buffer
                self.state = 0  # 将状态重置为未就绪
                self.save()
                return True, "Queue updated successfully."
            else:
                return False, "Queue is not empty. Buffer not synced."

        elif self.state == 2:
            # 缓冲区正在计算中
            return False, "Buffer is currently being computed."

        # 缓冲区未计算
        return False, "Buffer is not ready."



    from django.db.transaction import on_commit
    import threading

    # 出队到current_problem
    def dequeue(self):
        # 正常出队并返回出队的问题problem_number
        if len(self.problem_numbers) > 0:
            removed_problem_number = self.problem_numbers.pop(0)
            try:
                # 根据problem_number获取Problem对象
                self.current_problem = Problem.objects.get(problem_number=removed_problem_number)
            except Problem.DoesNotExist:
                # 如果没有找到Problem，设置current_problem为None
                self.current_problem = None
        else:
            removed_problem_number = None
            self.current_problem = None  # 如果队列为空，则设置current_problem为None

        self.save()

        # 根据队列的当前长度和状态触发相应的操作
        current_length = len(self.problem_numbers)
        if current_length == 8 and self.state == 0:
            # 如果队列长度为8且状态为0，则异步执行缓冲区更新
            on_commit(lambda: threading.Thread(target=self.update_task, args=(self.user.id,)).start())
        elif current_length == 0:
            if self.state == 1:
                # 如果队列为空且状态为1，则同步缓冲区到队列
                self.sync_from_buffer_to_queue()
            elif self.state == 0:
                # 如果队列为空且状态为0，需要重新计算缓冲区
                on_commit(lambda: threading.Thread(target=self.update_task, args=(self.user.id,)).start())

        return removed_problem_number

    @classmethod
    def handle_response(cls, user_id, status):
        valid_statuses = {'AC', 'WA', 'RE', 'CE', 'TLE', 'MLE', 'PE', 'OLE', 'SKIP'}

        if status not in valid_statuses:
            return False, "Invalid status value"

        try:
            with transaction.atomic():
                # 获取用户 + 锁定推荐队列行，确保后续修改安全
                user = User.objects.get(id=user_id)
                queue_instance = RecommendationPoolQueue.objects.select_for_update().get(user=user)

                # 当前题目为空，无法处理
                current_problem = queue_instance.current_problem
                if not current_problem:
                    return False, "No current problem in queue"

                # 添加历史记录
                RecommendationHistory.create_record(user, current_problem, timezone.now(), status)

                # 添加到撤销池
                obj, created = UserUndoPool.objects.get_or_create(user=user, problem=current_problem)

                # 如果是 AC，加入 ac_list
                if status == 'AC':
                    queue_instance.ac_list.append(current_problem.problem_number)
                    queue_instance.save()

                # 出队处理（假设 dequeue() 也是修改 queue_instance 的）
                queue_instance.dequeue()

                return True, "Handled response successfully"

        except User.DoesNotExist:
            return False, f"No user found with ID {user_id}"
        except RecommendationPoolQueue.DoesNotExist:
            return False, f"No recommendation queue found for user ID {user_id}"
        except Exception as e:
            return False, f"Unexpected error: {str(e)}"

    # 获取当前题目方法，仅当当前题目为空才会调用出栈方法，否则出栈行为只由response调用
    @classmethod
    def get_current_recommendation(cls, user_id):
        try:
            # 获取对应用户的推荐队列实例
            queue_instance = cls.objects.get(user__id=user_id)
        except cls.DoesNotExist:
            return None, "No recommendation queue found for this user."

        # 检查当前推荐题目是否存在
        if not queue_instance.current_problem:
            # 如果当前没有推荐题目，执行出队操作更新
            queue_instance.dequeue()

        # 再次检查current_problem是否更新
        if not queue_instance.current_problem:
            return None, "No available problems in the recommendation queue."

        return queue_instance.current_problem, "Current recommendation retrieved successfully."

    @classmethod
    def create_or_get_queue(cls, user_id):
        try:
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return False, f"No user found with ID {user_id}"

        # 使用get_or_create获取或创建Queue实例
        queue_instance, created = cls.objects.get_or_create(user=user)

        if created:
            # 如果是新创建的，则异步调用update_task为其初始化
            print(f"Created new queue for user {user.username}")
            # threading.Thread(target=cls.update_task, args=(user_id,)).start()
            # 串行调用update_task为其初始化
            cls.update_task(user_id)
            try:
                # 获取对应用户的推荐队列实例
                queue_instance_new = cls.objects.get(user__id=user_id)
                queue_instance_new.sync_from_buffer_to_queue()
                if not queue_instance_new.current_problem:
                    queue_instance_new.dequeue()
            except cls.DoesNotExist:
                return None, "No recommendation queue found for this user."

        else:
            print(f"Queue for user {user.username} already exists")

        return True, "Queue initialization complete"

    def count_problems(self):
        """计算队列中剩余题目的数量"""
        return len(self.problem_numbers)

    # 删除该用户的PoolQueue实例
    @classmethod
    def delete_queue(cls, user_id):
        """
        删除对应用户的 PoolQueue 实例
        """
        try:
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return False, f"No user found with ID {user_id}"

        try:
            queue_instance = cls.objects.get(user=user)
            queue_instance.delete()
            return True, "Queue deleted successfully"
        except cls.DoesNotExist:
            return False, f"No queue found for user ID {user_id}"
        except Exception as e:
            return False, f"Error deleting queue: {str(e)}"


# recommendationStateMachine基于UserRecommendationPool开发，每两次获得题目就发生刷新，且AC题目的更新由该模型对应的响应视图函数执行
# 希望设计一个新的推题队列，数据更新和取大值刷新均由模型控制，而非视图函数，视图函数仅用来 传递题目 和 接受响应。为此需要在推题队列类里加一个AC缓冲列表
class RecommendationStateMachine(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
    state = models.IntegerField(default=0)  # 0 for max score problem, 1 for second max score problem, 2 for wait
    max_score_problem = models.ForeignKey(Problem, related_name='max_score_users', null=True, on_delete=models.SET_NULL)
    second_max_score_problem = models.ForeignKey(Problem, related_name='second_max_score_users', null=True, on_delete=models.SET_NULL)

    def __str__(self):
        return f"{self.user.username} - State: {self.state}"

    def switch_state(self, new_state=None):
        """Switch the state explicitly or toggle between 0 and 1 if no new state is given."""
        if new_state is not None:
            self.state = new_state
        else:
            self.state = 1 if self.state == 0 else 0
        self.save()

    def update_problems(self, max_problem, second_max_problem):
        # 只要调用完成就代表状态应该转为就绪态
        self.max_score_problem = max_problem
        self.second_max_score_problem = second_max_problem
        self.switch_state(0)  # Set state back to ready after update

    # update_task需要外移
    @staticmethod
    def update_task(user_id):
        """Static method to perform asynchronous update of recommendation data."""
        try:
            instance = RecommendationStateMachine.objects.get(user_id=user_id)
            max_problem, second_max_problem = UserRecommendationPool.pop_top_two_scores(user_id)
            instance.update_problems(max_problem, second_max_problem)
            instance.switch_state(0)  # Restore to normal state after update
        except RecommendationStateMachine.DoesNotExist:
            print(f"No RecommendationStateMachine found for user ID {user_id}")
    def get_problem_and_switch(self):
        # 若状态处于等待态
        if self.state == 2:
            return None
        current_problem = self.max_score_problem if self.state == 0 else self.second_max_score_problem

        if self.state == 1:
            # 转换状态为 2
            self.switch_state(2)
            # Running the update task after the current transaction commits
            # 在数据库事务提交后才开始执行任务
            on_commit(lambda: threading.Thread(target=self.update_task, args=(self.user_id,)).start())
        else:
            self.switch_state()

        return current_problem

    @classmethod
    def create_or_get_instance(cls, user_id):
        try:
            user = User.objects.get(id=user_id)  # 尝试获取已存在的User实例
            instance, created = cls.objects.get_or_create(user=user)
            return instance, created  # 返回实例和是否新创建的布尔值
        except User.DoesNotExist:
            return None, False  # 如果用户不存在，则返回None和False

    @classmethod
    def delete_state_machine(cls, user_id):
        try:
            # 尝试获取并删除 RecommendationStateMachine 实例
            instance = cls.objects.get(user_id=user_id)
            instance.delete()
            return True, "State machine deleted successfully."
        except cls.DoesNotExist:
            # 未找到实例时返回错误信息
            return False, f"No state machine found for user with ID {user_id}."
        except Exception as e:
            # 捕获其他异常，并返回异常信息
            return False, f"Error occurred while deleting the state machine: {str(e)}"
